﻿/* Copyright 2009 dnAnalytics Project.
 *
 * Contributors to this file:
 * Marcus Cuda
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this 
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the dnAnalytics Project nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
using System;
using System.Collections.Generic;
using System.Threading;

namespace dnAnalytics.Threading
{
    /// <summary>
    /// Provides support for parallel loops. 
    /// </summary>
    internal static class Parallel
    {
        /// <summary>
        /// Executes a for loop in which iterations may run in parallel. 
        /// </summary>
        /// <param name="fromInclusive">The start index, inclusive.</param>
        /// <param name="toExclusive">The end index, exclusive.</param>
        /// <param name="body">The body to be invoked for each iteration.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="body"/> argument is null.</exception>
        /// <exception cref="AggregateException">At least one invocation of the body threw an exception.</exception>
        public static void For(int fromInclusive, int toExclusive, Action<int> body)
        {
            if (body == null)
            {
                throw new ArgumentNullException("body");
            }
            var count = toExclusive - fromInclusive;
            var size = count/ThreadQueue.ThreadCount;

            if (count < 1)
            {
                return;
            }

            var actions = new List<Action>();

            for (var i = 0; i < ThreadQueue.ThreadCount - 1; i++)
            {
                var start = fromInclusive + (i*size);
                var stop = fromInclusive + ((i + 1)*size);
                actions.Add(
                    () =>
                        {
                            for (var j = start; j < stop; j++)
                            {
                                body(j);
                            }
                        }
                    )
                    ;
            }
            actions.Add(
                () =>
                    {
                        for (var i = fromInclusive + ((ThreadQueue.ThreadCount - 1)*size); i < toExclusive; i++)
                        {
                            body(i);
                        }
                    }
                );
            Invoke(actions.ToArray());
        }

        /// <summary>
        /// Executes each of the provided actions inside a discrete, asynchronous task. 
        /// </summary>
        /// <param name="actions">An array of actions to execute.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="actions"/> argument is null.</exception>
        /// <exception cref="ArgumentException">The actions array contains a null element.</exception>
        /// <exception cref="AggregateException">An action threw an exception.</exception>
        public static void Invoke(params Action[] actions)
        {
            if (actions == null)
            {
                throw new ArgumentNullException("actions");
            }

            var tasks = new List<Task>();
            foreach (var action in actions)
            {
                if (action == null)
                {
                    throw new ArgumentException(Properties.Resources.NullAction);
                }
                tasks.Add(new Task(action));
            }
            foreach (var task in tasks)
            {
                ThreadQueue.Enqueue(task);
            }
            
            if(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
            {
                //not sure if this the best approach for STA
                foreach (var task in tasks)
                {
                    task.WaitOne();
                }
            }else
            {
                WaitHandle.WaitAll(tasks.ToArray());
            }

            var exceptions = new List<Exception>();
            foreach (var task in tasks)
            {
                if (task.ThrowException)
                {
                    exceptions.Add(task.Exception);
                }

                //this calls dispose
                task.Close();
            }
            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions);
            }
        }
    }
}